package team.szm.scd.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.extern.slf4j.Slf4j;
import lombok.var;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import team.szm.scd.config.NormalConfig;
import team.szm.scd.database.entity.Doctor;
import team.szm.scd.database.entity.Register;
import team.szm.scd.database.mapper.RegisterMapper;
import team.szm.scd.database.properties.AppointmentTimeType;
import team.szm.scd.database.vo.RegisterRemainVO;
import team.szm.scd.database.vo.RegisterVO;
import team.szm.scd.service.interfaces.IDoctorService;
import team.szm.scd.service.interfaces.IRegisterService;

import javax.annotation.Resource;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;

@Service
@Slf4j
public abstract class AbstractRegisterService implements IRegisterService {
    @Resource
    RegisterMapper mapper;

    @Override
    public List<RegisterVO> userRegisterInfo(String userId, Integer hospitalId) {
        QueryWrapper<RegisterVO> wrapper = new QueryWrapper<>();
        wrapper.eq("uid", userId);
        wrapper.eq("hid", hospitalId);
        return mapper.registerVOListFromWrapper(wrapper);
    }

    @Override
    public List<RegisterVO> registersDoctorHave(String doctorId, LocalDate today) {
        QueryWrapper<RegisterVO> wrapper = new QueryWrapper<>();
        wrapper.eq("appointment_day", today);
        wrapper.eq("did", doctorId);
        return mapper.registerVOListFromWrapper(wrapper);
    }

    @Resource
    IDoctorService doctorService;

    @Resource
    @Qualifier("ScdExecutor")//指定使用哪个实例化对象
    Executor executor;

    @Override
    public List<RegisterRemainVO>
    registerRemainInfo(int hospitalId, int departmentId, LocalDate today) {
        List<RegisterRemainVO> result = Collections.synchronizedList(new ArrayList<>());
        //一周7天，初始化大小为7
        List<CompletableFuture<Void>> mainTasks = new ArrayList<>(7);
        for (int i = 0; i < 7; i++) {
            final int finalI = i;
            mainTasks.add(CompletableFuture.supplyAsync(() -> {
                RegisterRemainVO vo = new RegisterRemainVO();
                LocalDate date = today.plusDays(finalI);
                vo.setDate(date);
                vo.setDayOfWeek(date.getDayOfWeek());
                List<CompletableFuture<Void>> innerTasks =
                        new ArrayList<>(AppointmentTimeType.values().length);
                Map<String, RegisterRemainVO.Details> innerResult =
                        new ConcurrentHashMap<>(16);
                RegisterRemainVO.Details nonExpertDetails = new RegisterRemainVO.Details();
                nonExpertDetails.setDoctorId(Doctor.NonExpertGroupId);
                nonExpertDetails.setPrice(NormalConfig.NonExpertRegisterPrice);
                nonExpertDetails.setDoctorName(Doctor.NonExpertGroupName);
                innerResult.put(Doctor.NonExpertGroupId, nonExpertDetails);
                for (var v : AppointmentTimeType.values()) {
                    innerTasks.add(CompletableFuture.supplyAsync(() -> {
                        List<Map<String, Object>> l = mapper.doctorRegisterRemainInfo(
                                hospitalId, departmentId, date, v);
                        log.trace("result length: " + l.size());
                        HashSet<String> nonExpertDoctors = new HashSet<>();
                        //线程安全的原子类型
                        AtomicInteger nowRemain = new AtomicInteger();
                        for (var m : l) {
                            log.trace(m.toString());
                            String doctorId = (String) m.get("did");
                            if (m.get("expert") instanceof Boolean && ((Boolean) m.get("expert"))) {
                                synchronized (innerResult) {
                                    RegisterRemainVO.Details details =
                                            innerResult.getOrDefault(doctorId, new RegisterRemainVO.Details());
                                    details.setDoctorName("专家号:" + m.get("dname"));
                                    details.setRemain(v,
                                            NormalConfig.ExpertDoctorRegisterNumberOnePeriod -
                                                    ((Long) m.get("count")).intValue()
                                    );
                                    details.setPrice(NormalConfig.ExpertRegisterPrice);
                                    details.setDoctorId(doctorId);
                                    innerResult.put(doctorId, details);
                                }
                            } else {
                                log.trace("count from database: " + m.get("count"));
                                log.trace("before: " + nowRemain);
                                nowRemain.addAndGet(-((Long) m.get("count")).intValue());
                                log.trace("after: " + nowRemain);
                                nonExpertDoctors.add(doctorId);
                            }
                        }
                        nonExpertDetails.setRemain(v,
                                NormalConfig.NonExpertDoctorRegisterNumberOnePeriod * nonExpertDoctors.size()
                                        + nowRemain.get()
                        );
                        return null;
                    }, executor));
                }
                try {
                    CompletableFuture.allOf(innerTasks.toArray(new CompletableFuture<?>[0])).get();
//                    for (var c : innerTasks) {
//                        c.get();
//                    }
                } catch (Exception e) {
                    log.error(e.toString(), e);
                    throw new RuntimeException(e);
                }
                vo.setDetailsMap(innerResult);
                result.add(vo);
                return null;
            }, executor));
        }
        try {
            CompletableFuture.allOf(mainTasks.toArray(new CompletableFuture<?>[0])).get();
//            for (var c : mainTasks) {
//                c.get();
//            }
        } catch (InterruptedException e) {
            log.error(e.toString(), e);
        } catch (Exception e) {
            log.error(e.toString(), e);
            throw new RuntimeException(e);
        }
        log.trace(result.toString());
        result.sort(Comparator.comparing(RegisterRemainVO::getDate));
        return result;
    }

    /**
     * 不允许多个用户同一时间进行新增挂号的操作
     */
    @Override
    public synchronized boolean addRegister(Register register, LocalDateTime now) {
        register.setRegisterTime(now);
        if (register.getDoctorId().equals(Doctor.NonExpertGroupId)) {
            List<Map<String, Object>> doctors = mapper.doctorRegisterRemainInfo(
                    register.getHospitalId(),
                    register.getDepartmentId(),
                    register.getAppointmentDay(),
                    register.getAppointmentTimeType()
            );
            Optional<Map<String, Object>> dOp = doctors.stream().
                    filter(v -> v.containsKey("expert") && !(Boolean) v.get("expert"))
                    .findFirst();
            if (dOp.isPresent()) {
                Map<String, Object> dMap = dOp.get();
                String doctorId = (String) dMap.get("did");
                if (doctorId != null) {
                    if (registerRemain(doctorId, register.getAppointmentDay(),
                            register.getAppointmentTimeType()) <= 0) {
                        return false;
                    }
                    register.setDoctorId(doctorId);
                    return mapper.insert(register) == 1;
                }
            }
            return false;
        } else {
            if (registerRemain(register.getDoctorId(), register.getAppointmentDay(),
                    register.getAppointmentTimeType()) <= 0) {
                return false;
            }
            return mapper.insert(register) == 1;
        }
    }

    protected int registerRemain(String doctorId, LocalDate appointmentTime,
                              AppointmentTimeType timeType) {
        int have = mapper.registeredNumberInAPeriod(doctorId, appointmentTime, timeType);
        return NormalConfig.ExpertDoctorRegisterNumberOnePeriod - have;
    }
}
